package com.xy.iot.netty.connect;

import cn.hutool.core.util.ObjectUtil;
import com.xy.iot.common.enums.DeviceHandleEnum;
import com.xy.iot.common.result.ChannelServiceEventData;
import com.xy.iot.common.utils.CacheUtils;
import com.xy.iot.common.utils.IpUtil;
import com.xy.iot.common.utils.SpringContextUtil;
import com.xy.iot.netty.device.DeviceServerChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;

import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;

/**
 * 处理连接成功、断开连接 相关事件的处理
 * description:
 *      1、channelRead(ChannelHandlerContext ctx, Object msg):
 *              当有消息从网络读取到时调用。你可以在此方法中处理接收到的消息。
 *      2、channelRegistered(ChannelHandlerContext ctx):
 *              当 Channel 被注册到它的 EventLoop 时调用。
 *      3、channelUnregistered(ChannelHandlerContext ctx):
 *              当 Channel 从其 EventLoop 注销时调用。
 *      4、channelActive(ChannelHandlerContext ctx):
 *              当通道变得活动（即连接已建立）时调用。
 *      5、channelInactive(ChannelHandlerContext ctx):
 *              当通道不再活动（即连接被关闭或中断）时调用。
 *      6、exceptionCaught(ChannelHandlerContext ctx, Throwable cause):
 *              捕获到从 ChannelPipeline 中传播的异常时调用。这是一个重要的错误处理方法。
 *      7、channelReadComplete(ChannelHandlerContext ctx):
 *              当从 Channel 中读取数据的操作完成时调用，可以在此执行资源清理或准备下一次读取操作。
 */
@Slf4j
@Sharable
public class BaseServerConnect extends ChannelInboundHandlerAdapter {
    /**
     * 连接成功次数
     */
    private final Map<String, Integer> onLineMap = new HashMap<>();

    /**
     * 连接断开次数
     */
    private final Map<String, Integer> breakOffMap = new HashMap<>();

    /**
     *  构造方法
     */
    public BaseServerConnect(){

    }

    /**
     * 当通道变得活动（即连接已建立）时调用。
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        log.info("channelActive 当通道变得活动（即连接已建立）时调用。");
        //成功后，重连失败次数清零
        Channel channel = ctx.channel();
        ctx.fireChannelActive();
        ApplicationContext applicationContext = SpringContextUtil.getApplicationContext();
        SocketAddress socketAddress = channel.remoteAddress();
        if(ObjectUtil.isNotEmpty(socketAddress)){
            String address =  IpUtil.getAddress(socketAddress);
            DeviceServerChannel deviceChannel = new DeviceServerChannel(channel);
            int onLine = (ObjectUtil.isEmpty(onLineMap.get(deviceChannel.name())) ? 0 : onLineMap.get(deviceChannel.name())) + 1;
            onLineMap.put(deviceChannel.name(), onLine);
            ChannelServiceEventData connectServerData = new ChannelServiceEventData(this, DeviceHandleEnum.ON_LINE.getKey(), address, onLine);
            if(ObjectUtil.isNotEmpty(connectServerData) && ObjectUtil.isNotEmpty(applicationContext)){
                applicationContext.publishEvent(connectServerData);
                //将柜地址与通讯管道的绑定关系写入缓存
                CacheUtils.put(deviceChannel.name(), deviceChannel);
                log.info("设备与通讯管道的绑定关系，设备：{}", address);
            }
        }
    }

    /**
     * 当通道不再活动（即连接被关闭或中断）时调用。
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//        log.info("channelInactive 当通道不再活动（即连接被关闭或中断）时调用。");
        ApplicationContext applicationContext = SpringContextUtil.getApplicationContext();
        ctx.fireChannelInactive();
        Channel channel = ctx.channel();
        SocketAddress socketAddress = channel.remoteAddress();
        if(!ObjectUtil.isEmpty(channel) && !ObjectUtil.isEmpty(socketAddress)){
            String address = IpUtil.getAddress(socketAddress);
            DeviceServerChannel deviceChannel = new DeviceServerChannel(channel);
            int breakOff = (ObjectUtil.isEmpty(breakOffMap.get(deviceChannel.name())) ? 0 : breakOffMap.get(deviceChannel.name())) + 1;
            breakOffMap.put(deviceChannel.name(), breakOff);
            ChannelServiceEventData connectServerData = new ChannelServiceEventData(this, DeviceHandleEnum.BREAK_OFF.getKey(), address, breakOff);
            if(ObjectUtil.isNotEmpty(connectServerData) && ObjectUtil.isNotEmpty(applicationContext)){
                applicationContext.publishEvent(connectServerData);
            }
            //将通讯管道的绑定关系从缓存中删除
            CacheUtils.remove(deviceChannel.name());
            //连接断开后的最后处理
            ctx.pipeline().remove(ctx.handler());
            ctx.deregister();
            ctx.close();
            log.info("设备与通讯管道的断开，设备：{}", address);
        }
    }


}
